/*
Copyright (c) 2009 Hayden Curran <haydenz@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
        this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
        this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
    * The names of its contributors may not be used to endorse or promote
        products derived from this software without specific prior
        written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "irc.h"

void irc_init(buffer_t *irc_buffer, pug_t *pug) {
    buffer_init(irc_buffer);
    pug_init(pug);
}

/*
    Sets up a socket and connects to the specified IRC server
    Sends the specified nickname
    Returns the socket descriptor
*/
int irc_connect(void) {
    struct addrinfo hints, *res;
    int ircfd, ret;
    char buf[BUFFER_LENGTH];

    // first, load up address structs with getaddrinfo():
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    getaddrinfo(IRC_SERVER, IRC_PORT, &hints, &res);

    // make a socket:
    ircfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

    // connect!
    ret = connect(ircfd, res->ai_addr, res->ai_addrlen);
    if (ret == -1) {
        perror("connect");
        exit(1);
    }

    sprintf(buf, "NICK %s\r\n", IRC_NICK);
    irc_send(ircfd, buf);
    sprintf(buf, "USER %s %s %s :%s\r\n",
        IRC_NICK, IRC_NICK, IRC_SERVER, IRC_NICK);
    irc_send(ircfd, buf);

    freeaddrinfo(res);

    return ircfd;
}

/*
    Handles recv on ircfd
*/
int irc_receive(int ircfd, buffer_t *irc_buffer, pug_t *pug) {
    int ret;
    char buf[BUFFER_LENGTH];
    char packet[PACKET_LENGTH];

    char *name;

    char *servtoks[4];
    char *server_tok; // server tokens
    char *argtoks[4];
    char *arg_tok; // argument tokens
    // eg:
    // packet = :Snoke.NL.EU.GameSurge.net 332 OkonkPug
    //          #OkonkPugBot :Hi how are you my name is: Okonk
    // server_tok = Snoke.NL.EU.GameSurge.net 332 OkonkPug #OkonkPugBot
    // arg_tok = Hi how are you my name is: Okonk

    ret = recv(ircfd, buf, sizeof(buf), 0);
    if (ret <= 0) { // Closed or error
        if (ret == 0) {
            printf("IRC closed connection.\n");
        }
        else {
            perror("recv");
        }

        #ifdef WIN32
        closesocket(ircfd);
        #else
        close(ircfd);
        #endif

        return ret;
    }
    else { // Received data
        ret = buffer_receive(irc_buffer, buf, ret);

        while (buffer_get_packet(irc_buffer, packet) > 0) {
            //printf("%s\n", packet);

            server_tok = strtok(packet, ":");
            arg_tok = strtok(NULL, "");

            if (strncmp(server_tok, "PING ", 5) == 0) {
                sprintf(buf, "PONG :%s\r\n", arg_tok);
                irc_send(ircfd, buf);
            }
            else {
                servtoks[0] = strtok(server_tok, " ");
                servtoks[1] = strtok(NULL, " ");
                servtoks[2] = strtok(NULL, " ");
                servtoks[3] = strtok(NULL, " ");

                if (strncmp(servtoks[1], "001", 3) == 0) {
                    sprintf(buf, "JOIN %s\r\n", IRC_CHAN);
                    irc_send(ircfd, buf);
                }

                else if (strncmp(servtoks[1], "PRIVMSG", 7) == 0) {
                    argtoks[0] = strtok(arg_tok, " ");
                    argtoks[1] = strtok(NULL, " ");
                    argtoks[2] = strtok(NULL, " ");
                    argtoks[3] = strtok(NULL, " ");

                    if (strcmp(servtoks[2], IRC_CHAN) == 0) {
                        irc_process_chan_msg(ircfd, servtoks, argtoks,
                            irc_buffer, pug);
                    }
                    else if (strcmp(servtoks[2], IRC_NICK) == 0) {

                    }
                }

                else if (strncmp(servtoks[1], "PART", 4) == 0) {
                    name = strtok(servtoks[0], "!");

                    if (pug_player(pug, name)) {
                        irc_pug_leave(ircfd, pug, name);
                    }
                }

                else if (strncmp(servtoks[1], "QUIT", 4) == 0) {
                    name = strtok(servtoks[0], "!");

                    if (pug_player(pug, name)) {
                        irc_pug_leave(ircfd, pug, name);
                    }
                }
            }
        }
    }

    return 1;
}

/*
    Sends null terminated string, buf to ircfd
    Returns the characters sent
*/
int irc_send(int ircfd, char *buf) {
    int ret = send(ircfd, buf, strlen(buf), 0);
    if (ret == -1) {
        perror("send");
    }
    return ret;
}

/*
    Called when we received a PRIVMSG from the channel.

*/
int irc_process_chan_msg(int ircfd, char **servtoks, char **argtoks,
    buffer_t *irc_buffer, pug_t *pug)
{
    char buf[BUFFER_LENGTH];
    char *name;

    if (strncmp(argtoks[0], "!pug", 4) == 0) {
        switch (pug->in_progress) {
            case NOT_STARTED:
                name = strtok(servtoks[0], "!");

                if (argtoks[1] != NULL) {

                }
                else {
                    pug_create(pug, name, DEFAULT_MAX_PLAYERS);
                    irc_msg_pug_created(ircfd, pug);
                }
                break;

            case IN_GAME:
                sprintf(buf, "PRIVMSG %s :There is already a PUG in progress. "
                    "Please wait until it finishes.\r\n", IRC_CHAN);
                irc_send(ircfd, buf);
                break;

            case NEED_PLAYERS:
                sprintf(buf, "PRIVMSG %s :A %d player PUG is already started. "
                    "Type !j to join. (%d/%d) players.\r\n",
                    IRC_CHAN, pug->max_players,
                    pug->cur_players, pug->max_players);
                irc_send(ircfd, buf);
                break;
        }
    }
    else if (strcmp(argtoks[0], "!j") == 0) {

        switch (pug->in_progress) {
            case NOT_STARTED:
                irc_msg_no_pug_started(ircfd, pug);
                break;

            case IN_GAME:
                sprintf(buf, "PRIVMSG %s :There is already a PUG in progress. "
                    "Please wait until it finishes.\r\n", IRC_CHAN);
                irc_send(ircfd, buf);
                break;

            case NEED_PLAYERS:
                name = strtok(servtoks[0], "!");

                if (pug_player(pug, name)) {
                    sprintf(buf, "PRIVMSG %s :%s, you are already in the "
                        "PUG.\r\n", IRC_CHAN, name);
                    irc_send(ircfd, buf);
                }
                else {
                    pug_join(pug, name);
                    irc_msg_pug_join(ircfd, pug, name);
                    if (pug->in_progress == IN_VOTE) {
                        irc_msg_pug_vote_start(ircfd, pug);
                    }
                }
                break;
        }
    }

    else if (strcmp(argtoks[0], "!l") == 0) {
        name = strtok(servtoks[0], "!");

        if (pug_player(pug, name)) {
            irc_pug_leave(ircfd, pug, name);
        }
    }

    else if (strcmp(argtoks[0], "!vote") == 0) {
        name = strtok(servtoks[0], "!");

        if (pug->in_progress == IN_VOTE) {
            if (pug_player(pug, name)) {
                if (argtoks[1] != NULL) {
                    if (pug_map(pug, argtoks[1])) {
                        pug_map_vote(pug, name, argtoks[1]);
                        irc_msg_pug_map_vote(ircfd, pug, name);

                        if (pug->in_progress == IN_GAME) {
                            irc_pug_voting_finished(ircfd, pug);
                        }
                    }
                }
            }
        }
    }

    else if (strcmp(argtoks[0], "!status") == 0) {
        switch (pug->in_progress) {
            case NOT_STARTED:
                irc_msg_no_pug_started(ircfd, pug);
                break;

            case NEED_PLAYERS:
                sprintf(buf, "PRIVMSG %s :%d player PUG needs "
                    "players. Type !j to join.\r\n",
                    IRC_CHAN, pug->max_players);
                irc_send(ircfd, buf);
                break;

            case IN_VOTE:
                sprintf(buf, "PRIVMSG %s :%d player PUG in voting.\r\n",
                    IRC_CHAN, pug->max_players);
                irc_send(ircfd, buf);
                break;

            case IN_GAME:
                sprintf(buf, "PRIVMSG %s :%d player PUG in progress.\r\n",
                    IRC_CHAN, pug->max_players);
                irc_send(ircfd, buf);
                break;
        }
    }

    else if (strcmp(argtoks[0], "!players") == 0) {
        if (pug->in_progress == NOT_STARTED) {
            irc_msg_no_pug_started(ircfd, pug);
        }
        else {
            irc_msg_pug_player_list(ircfd, pug);
        }
    }

    return 1;
}

/*
    When someone leaves the pug, either by parting, quitting or by !l
*/
int irc_pug_leave(int ircfd, pug_t *pug, char *name) {
    int captainleft = 0;

    if (pug->in_progress == NEED_PLAYERS ||
        pug->in_progress == IN_VOTE)
    {
        if (strcmp(pug->captain_name, name) == 0) {
            captainleft = 1;
        }
        pug_leave(pug, name);
        irc_msg_pug_leave(ircfd, pug, name);
        if (captainleft) {
            if (pug->cur_players == 0) {
                irc_msg_pug_disbanded(ircfd, pug);
                pug_clear(pug);
            }
            else {
                irc_msg_pug_new_captain(ircfd, pug);
            }
        }
        return 1;
    }
    return 0;
}

/*
    Called when voting is finished, either by everyone voting or
    by a timer
*/
int irc_pug_voting_finished(int ircfd, pug_t *pug) {
    pug_voting_finished(pug);

    if (pug->in_progress == IN_GAME) {
        irc_msg_pug_voting_finished(ircfd, pug);
        irc_msg_pug_server_details(ircfd, pug);
    }
    return 1;
}

/*
    Called when a pug is created
    Sets the channel topic, announces to the channel.
    Asks the captain for server details and rcon.
*/
int irc_msg_pug_created(int ircfd, pug_t *pug) {
    char buf[BUFFER_LENGTH];

    sprintf(buf, "TOPIC %s :Okonk's TF2 PUGs. %d player PUG needs players. "
        "Type !j to join.\r\n",
        IRC_CHAN, pug->max_players);
    irc_send(ircfd, buf);

    sprintf(buf, "PRIVMSG %s :%d player PUG started by %s."
        "Type !j to join.\r\n",
        IRC_CHAN, pug->max_players, pug->captain_name);
    irc_send(ircfd, buf);

    return 1;
}

/*
    Called when someone joins the pug
    Messages the channel they successfully joined and the new player count.
*/
int irc_msg_pug_join(int ircfd, pug_t *pug, char *name) {
    char buf[BUFFER_LENGTH];

    sprintf(buf, "PRIVMSG %s :%s joined the PUG. (%d/%d) players.\r\n",
        IRC_CHAN, name, pug->cur_players, pug->max_players);
    irc_send(ircfd, buf);

    return 1;
}

/*
    Called when we have enough players
*/
int irc_msg_pug_vote_start(int ircfd, pug_t *pug) {
    char buf[BUFFER_LENGTH];
    int i;

    sprintf(buf, "PRIVMSG %s :Vote for a map to play on out of ",
        IRC_CHAN);
    for (i = 0; i < pug->nmaps; i++) {
        strcat(buf, pug->maps[i]);
        if (i != pug->nmaps - 1) strcat(buf, ", ");
    }
    strcat(buf, "\r\n");
    irc_send(ircfd, buf);
    sprintf(buf, "PRIVMSG %s :To vote, type !vote <map name> eg "
        "!vote cp_badlands\r\n", IRC_CHAN);
    irc_send(ircfd, buf);

    return 1;
}

/*
    Called when someone leaves the pug
*/
int irc_msg_pug_leave(int ircfd, pug_t *pug, char *name) {
    char buf[BUFFER_LENGTH];

    sprintf(buf, "PRIVMSG %s :%s left the PUG. (%d/%d) players.\r\n",
        IRC_CHAN, name, pug->cur_players, pug->max_players);
    irc_send(ircfd, buf);

    return 1;
}

/*
    Everyone left the pug
*/
int irc_msg_pug_disbanded(int ircfd, pug_t *pug) {
    char buf[BUFFER_LENGTH];

    sprintf(buf, "TOPIC %s :Okonk's TF2 PUGs. No PUG in progress. "
        "To start a PUG type !pug [players]\r\n",
        IRC_CHAN);
    irc_send(ircfd, buf);
    sprintf(buf, "PRIVMSG %s :The PUG has been disbanded. "
        "To start a new PUG type !pug [players]\r\n",
        IRC_CHAN);
    irc_send(ircfd, buf);

    return 1;
}

/*
    Old captain left so new one assigned
*/
int irc_msg_pug_new_captain(int ircfd, pug_t *pug) {
    char buf[BUFFER_LENGTH];

    sprintf(buf, "PRIVMSG %s :The new captain is %s.\r\n",
        IRC_CHAN, pug->captain_name);
    irc_send(ircfd, buf);

    return 1;
}

/*
    player voted on a map
*/
int irc_msg_pug_map_vote(int ircfd, pug_t *pug, char *player) {
    char buf[BUFFER_LENGTH];
    int i, j, mapno;
    int votecount = 0;

    for (i = 0; i < pug->cur_players; i++) {
        if (strcmp(player, pug->players[i]) == 0) {
            mapno = pug->votes[i];
            for (j = 0; j < pug->cur_players; j++) {
                if (pug->votes[j] == mapno) votecount++;
            }
        }
    }

    sprintf(buf, "PRIVMSG %s :%s voted %s (%d).\r\n",
        IRC_CHAN, player, pug->maps[mapno], votecount);
    irc_send(ircfd, buf);

    return 1;
}

/*
    Messages the channel when voting is finished
*/
int irc_msg_pug_voting_finished(int ircfd, pug_t *pug) {
    char buf[BUFFER_LENGTH];
    int i;
    int *totals = malloc(sizeof(int) * pug->nmaps);
    int highest = 0;

    memset(totals, 0, sizeof(int) * pug->nmaps);

    for (i = 0; i < pug->cur_players; i++) {
        if (pug->votes[i] != -1) {
            totals[i] = totals[i] + 1;
        }
    }

    for (i = 0; i < pug->nmaps; i++) {
        if (totals[i] > totals[highest]) highest = i;
    }

    sprintf(buf, "TOPIC %s :Okonk's TF2 PUGs. %d player PUG in progress.\r\n",
        IRC_CHAN, pug->max_players);
    irc_send(ircfd, buf);

    sprintf(buf, "PRIVMSG %s :%d voted for %s (%f%%).\r\n",
        IRC_CHAN, totals[highest], pug->maps[highest],
        ((float)totals[highest]/(float)pug->cur_players));
    irc_send(ircfd, buf);

    return 1;
}

/*
    Private messages everyone in the PUG the server details
*/
int irc_msg_pug_server_details(int ircfd, pug_t *pug) {
    char buf[BUFFER_LENGTH];
    int i;

    for (i = 0; i < pug->cur_players; i++) {
        sprintf(buf, "PRIVMSG %s :Server info, connect immediately:"
            "connect %s:%s;password %s\r\n",
            pug->players[i], pug->server_address, pug->server_port,
            pug->server_password);
        irc_send(ircfd, buf);
    }

    return 1;
}

/*
    Messages the channel the current players
*/
int irc_msg_pug_player_list(int ircfd, pug_t *pug) {
    char buf[BUFFER_LENGTH];
    int i;

    sprintf(buf, "PRIVMSG %s :Players: %s\r\n",
            IRC_CHAN, pug->players[0]);

    for (i = 1; i < pug->cur_players; i++) {
        strcat(buf, ",");
        strcat(buf, pug->players[i]);
    }
    irc_send(ircfd, buf);

    return 1;
}

/*
    Messages the channel no pug started
*/
int irc_msg_no_pug_started(int ircfd, pug_t *pug) {
    char buf[BUFFER_LENGTH];

    sprintf(buf, "PRIVMSG %s :No PUG in progress. "
        "To start a PUG type !pug [players]\r\n",
        IRC_CHAN);
    irc_send(ircfd, buf);
    return 1;
}
